package cn.ztydata.teachingsystem.heaven.service;

import cn.ztydata.teachingsystem.common.Page;
import cn.ztydata.teachingsystem.exception.ServiceException;
import cn.ztydata.teachingsystem.heaven.common.WebContext;
import cn.ztydata.teachingsystem.heaven.cons.FileNameCode;
import cn.ztydata.teachingsystem.heaven.cons.Roles;
import cn.ztydata.teachingsystem.heaven.cons.StudentAnswerStatus;
import cn.ztydata.teachingsystem.heaven.cons.StudentTestStatus;
import cn.ztydata.teachingsystem.heaven.dao.*;
import cn.ztydata.teachingsystem.heaven.dao.redis.ExerciseCountDao;
import cn.ztydata.teachingsystem.heaven.dao.redis.StudentAnswerRedisDao;
import cn.ztydata.teachingsystem.heaven.dao.redis.TestKpDao;
import cn.ztydata.teachingsystem.heaven.dao.redis.TestSubmittedCounterDao;
import cn.ztydata.teachingsystem.heaven.entity.*;
import cn.ztydata.teachingsystem.heaven.entity.exercise.Exercise;
import cn.ztydata.teachingsystem.heaven.entity.test.StudentAnswer;
import cn.ztydata.teachingsystem.heaven.entity.test.StudentTest;
import cn.ztydata.teachingsystem.heaven.entity.test.Test;
import cn.ztydata.teachingsystem.heaven.entity.test.TestExercise;
import cn.ztydata.teachingsystem.heaven.util.FileConverter;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.sql.Date;
import java.sql.Timestamp;
import java.util.*;

/**
 * 作业测试服务类
 * Created by chenxin on 2015/3/18.
 */
@Service
public class TestService extends BaseService {
    @Autowired
    private ExerciseService exerciseService;
    @Autowired
    private StudentAnswerService studentAnswerService;

    @Autowired
    private TestDao testDao;
    @Autowired
    private ChapterDao chapterDao;
    @Autowired
    private TestSubmittedCounterDao testSubmittedCounterDao;
    @Autowired
    private CourseGroupDao courseGroupDao;
    @Autowired
    private TestExerciseDao testExerciseDao;
    @Autowired
    private TestKpDao testKpDao;
    @Autowired
    private StudentCourseDao studentCourseDao;
    @Autowired
    private StudentTestDao studentTestDao;
    @Autowired
    private KpDao kpDao;
    @Autowired
    private SpecialtyDao specialtyDao;
    @Autowired
    private ResourceDao resourceDao;
    @Autowired
    private StudentService studentService;
    @Autowired
    private ExerciseDao exerciseDao;
    @Autowired
    private ExerciseCountDao exerciseCountDao;
    @Autowired
    private StudentAnswerRedisDao studentAnswerRedisDao;

    @Autowired
    private FileConverter fileConverter;
    @Autowired
    private WebContext webContext;

    /**
     * 获取学生作业测试列表
     *
     * @param groupId 群组ID
     * @param type 作业类型
     * @param method 作业方式
     * @param pageNum 当前页码
     * @param pageSize 每页条数
     * @param search 搜索关键字
     * @return Page
     *
     * @author cx
     * @since 2015/03/19
     */
    public Page<Test> getListForStudent(final long groupId, final int type, final int method, final int pageNum,
                                         final int pageSize, final String search) {
        //获取作业列表
        Page<Test> pageTests = testDao.getListForStudent(groupId, type, method, pageNum, pageSize, search, webContext.getUserId());

        //批量查询章节的名称
        Set<Long> chapterIds = Sets.newHashSet();
        Map<Long, String> chapterNames = Maps.newHashMap();

        log.debug("循环分页作业集合");
        for (Test test : pageTests.getItems()) {
            chapterIds.add(test.getFirstLevelId());
            chapterIds.add(test.getSecondLevelId());
        }

        if (!chapterIds.isEmpty()) {
            log.debug("章节ID集合不为空:!chapterIds.isEmpty()");
            chapterNames = chapterDao.getNames(chapterIds);
        }

        log.debug("再次循环分页作业集合");
        for (Test test : pageTests.getItems()) {
            //绑定章节名称
            test.setFirstLevel(chapterNames.get(test.getFirstLevelId()));
            test.setSecondLevel(chapterNames.get(test.getSecondLevelId()));

            //进一步细化学生作业的状态，供前端作业操作显示判断用
            judgeStudentTestStatus(test);
        }

        log.debug("return 学生作业分页列表");
        return pageTests;
    }

    /**
     * 获取学生作业统计图信息
     *
     * @param groupId 群组ID
     * @param type 作业类型
     * @param method 作业方式
     * @return List
     *
     * @author cx
     * @since 2015/03/20
     */
    public List<Test> getStatisticList(final long groupId, final int type, final int method, final String keyword) {
        log.debug("获取学生作业统计图信息");
        return testDao.getGroupTestsWithGrade(groupId, type, method, webContext.getUserId(), keyword);
    }

    /**
     * 获取学生最近作业、测试列表
     *
     * @param type 作业类型
     * @param pageNum 当前页码
     * @param pageSize 每页条数
     * @return Page
     *
     * @author cx
     * @since 2015/3/20
     */
    public Page<Test> getLastListForStudent(final int type, final int pageNum, final int pageSize) {
        //获取登录学生当前学期的所有课程群组ID
        List<Long> groupIds = courseGroupDao.getIdsForStudent(webContext.getAccount());

        //登录学生当前学期没有课程群组
        if (groupIds.isEmpty()) {
            log.debug("登录学生当前学期没有课程群组");
            return new Page<>();
        }

        //获取作业分页列表
        Page<Test> testsPage = testDao.getLastTestsForStudent(groupIds, type, pageNum, pageSize, webContext.getUserId());

        log.debug("循环学生作业分页集合");
        for (Test test : testsPage.getItems()) {
            //进一步细化学生作业的状态，供前端作业操作显示判断用
            judgeStudentTestStatus(test);
        }

        log.debug("获取学生最近作业分页列表");
        return testsPage;
    }

    /**
     * 根据当前时间判断学生作业的状态
     *
     * @param test 作业实体类
     *
     * @author cx
     * @since 2015/4/6
     */
    private void judgeStudentTestStatus(final Test test) {
        //学生作业
        StudentTest studentTest = test.getStudentTest();
        //当前时间
        Date currentTime = new Date(new java.util.Date().getTime());
        //作业开始日期和结束时间
        Date startTime = test.getStartTime();
        Date endTime = test.getEndTime();

        if (currentTime.before(startTime)) { //作业未开始
            log.debug("作业未开始:currentTime.before(startTime)");

            studentTest.setStatus(StudentTestStatus.NOT_START);
        }
        else if (currentTime.after(endTime)) { //作业已结束
            if (studentTest.getStatus() == StudentTestStatus.NOT_SUBMITED) {
                log.debug("作业已结束且未提交:currentTime.after(endTime) && status == StudentTestStatus.NOT_SUBMITED");

                studentTest.setStatus(StudentTestStatus.IS_OVER);
            }
        }
        else { //作业未结束
            //隐藏作业得分
            studentTest.setScore(null);
        }
    }

    /**
     * 获取学生保存的线下作业
     *
     * @param testId
     * @return StudentTestStatus
     *
     * @author cx
     * @since 2015/3/20
     */
    public StudentTest getSavedTestFile(final long testId) {
        //校验作业是否属于登录学生
        checkStudentTest(testId);

        //获取登录学生保存过的线下作业
        StudentTest studentTest = testDao.getSavedTest(testId, webContext.getUserId());

        if (studentTest == null) {
            log.debug("学生线下作业为null:studentTest == null");
            studentTest = new StudentTest();
        }

        log.debug("获取学生保存的线下作业");
        return studentTest;
    }

    /**
     * 获取教师作业/测试列表
     * @param userId 登录用户编号
     * @param keyword 搜索关键字
     * @param groupId 课程群组编号
     * @param type 类型（0：作业，1：测试）
     * @param method 方式（0：线上，1：线下）
     * @param currentPage 页码
     * @param pageSize 每页显示条数
     * @return Page
     *
     * @author fanruiping
     * @since 2015-03-20
     */
    public Page<Test> getListForTeacher(Long courseId,Long chapterId,Long userId,String keyword,Long groupId,int type,int method,int currentPage,int pageSize){
        Page<Test> testPage = testDao.getListForTeacher(courseId,chapterId,userId, keyword, type, groupId, method, currentPage, pageSize);

        //判断数据是否为空
        if(testPage.getCount() == 0){
            log.debug("未获取到教师作业或测试列表数据:testPage.getCount() == 0");

            return testPage;
        }

        //批量查询章节的名称
        Set<Long> chapterIds = Sets.newHashSet();
        Map<Long, String> chapterNames = Maps.newHashMap();

        log.debug("循环获取作业所属章节编号:Test test : testPage.getItems()");
        //循环获取章节编号
        for (Test test : testPage.getItems()) {
            chapterIds.add(test.getFirstLevelId());
            chapterIds.add(test.getSecondLevelId());
        }

        //获取章节名称集合
        if (!chapterIds.isEmpty()) {
            log.debug("获取到作业所属章节编号集合:!chapterIds.isEmpty()");

            chapterNames = chapterDao.getNames(chapterIds);
        }

        //判断是否是获取指定教师创建的作业或测试列表
        if(userId != 0){
            log.debug("限制作业的创建人:userId != 0");

            //获取当前时间
            Date currentTime = new Date(new java.util.Date().getTime());

            log.debug("循环作业列表插入章节名称");
            //循环插入章节名称和提交人数
            for(Test test : testPage.getItems()){
                test.setFirstLevel(chapterNames.get(test.getFirstLevelId()));
                test.setSecondLevel(chapterNames.get(test.getSecondLevelId()));

                //作业提交人数
                test.setSubmittedNum((int) testSubmittedCounterDao.getSubmittedNum(test.getId()));

                //作业未开始
                if(currentTime.before(test.getStartTime())){
                    log.debug("作业未开始:currentTime.before(test.getStartTime())");

                    test.setStatus(0);
                    //作业已开始未结束
                }else if(currentTime.equals(test.getStartTime()) || (currentTime.after(test.getStartTime())) && currentTime.before(test.getEndTime())){
                    log.debug("作业已开始未结束:currentTime.equals(test.getStartTime()) || (currentTime.after(test.getStartTime())) && currentTime.before(test.getEndTime())");

                    test.setStatus(1);
                }else{//作业已结束
                    log.debug("作业已结束");

                    test.setStatus(2);
                }
            }
        }

        return testPage;
    }

    /**
     * 新增线下作业/测试
     * @param test 作业测试实体
     *
     * @author fanruiping
     * @since 2015-03-21
     */
    @Transactional
    public void addOffline(Test test){
        //设定创建时间
        test.setCreateTime(new Timestamp(System.currentTimeMillis()));
        //引用新增
        test.setIsRefNew(0);
        //设定学生人数
        test.setTotalNum(studentCourseDao.getStudentCntByCourse(test.getGroupId()));

        //未选择资源，设置资源编号为0
        if(test.getResourceId() == null || test.getResourceId() == 0){
            log.debug("新增作业为引用资源:test.getResourceId() == null");

            test.setResourceId(Long.valueOf(0));
        }else{
            //获取资源明细
            Resource resource = resourceDao.getFileDetail(test.getResourceId());
            //判断是否获取到资源明细，若有插入附件信息
            if(null != resource){
                log.debug("未获取到指定资源附件信息:null != resource");

                test.setTestFileName(resource.getOrigin());
                test.setTestDownloadPath(resource.getDownloadUrl());
                test.setTestShowPath(resource.getShowAddr());
            }
        }

        //新增作业/测试
        if(!testDao.addOffline(test)){
            log.debug("线下作业添加失败:!testDao.addOffline(test)");

            throw new ServiceException("作业或测试数据添加失败");
        }

        //判断是否需要为作业添加考点
        if(!test.getKnowledgePoints().isEmpty()){
            log.debug("线下作业的知识点集合不为空:!test.getKnowledgePoints().isEmpty()");

            testKpDao.addKp(test);
        }
    }

    /**
     * 新增线上作业
     * @param test 作业测试实体
     *
     * @author fanruiping
     * @since 2015-03-21
     */
    public void addOnline(Test test){
        //设定创建时间
        test.setCreateTime(new Timestamp(System.currentTimeMillis()));
        //引用新增
        test.setIsRefNew(0);
        //设定学生人数
        test.setTotalNum(studentCourseDao.getStudentCntByCourse(test.getGroupId()));
        //设定批阅状态
        test.setReadOver(0);

        //新增作业/测试
        if(!testDao.addOnline(test)){
            log.debug("线上作业新增失败:!testDao.addOnline(test)");

            throw new ServiceException("作业或测试数据添加失败");
        }

        //判断是否为作业或测试添加习题
        if(!test.getExercises().isEmpty()){
            log.debug("作业包含的习题集合不为空:!test.getExercises().isEmpty()");

            List<Long> exerciseIds = new ArrayList<>();

            log.debug("循环为习题集合插入作业编号:TestExercise testExercise : test.getExercises()");
            //循环插入作业/测试编号
            for(TestExercise testExercise : test.getExercises()){
                testExercise.setTestId(test.getId());

                //递增习题热度
                exerciseCountDao.increment(testExercise.getExerciseId(),ExerciseCountDao.USE_TOTAL);

                exerciseIds.add(testExercise.getExerciseId());
            }

            testExerciseDao.batchAdd(test.getExercises());

            //判断习题集合中是否含有主观题，若有设置作业为“待批阅”
            if(!exerciseDao.checkObjective(exerciseIds) && testDao.setHaveRead(test.getId()) == -1){
                throw new ServiceException("设置作业已批阅状态失败");
            }
        }

        //判断是否需要为作业添加考点
        if(!test.getKnowledgePoints().isEmpty()){
            log.debug("线上作业的知识点集合不为空:!test.getKnowledgePoints().isEmpty()");

            testKpDao.addKp(test);
        }
    }

    /**
     * 引用新增
     * @param test 作业实体（新增作业）
     * @param testId 作业编号（被引用）
     *
     * @author fanruiping
     * @since 2015-03-21
     */
    public void repeatAdd(Test test,Long testId){
        //获取被引用作业的详细信息
        Test refTest = testDao.getSimpleInfo(testId);

        //设定创建时间
        test.setCreateTime(new Timestamp(System.currentTimeMillis()));
        //设定学生人数
        test.setTotalNum(studentCourseDao.getStudentCntByCourse(test.getGroupId()));
        //引用新增
        test.setIsRefNew(1);
        //总分
        test.setScore(refTest == null ? 0 : refTest.getScore());
        //作业说明
        test.setRemark(refTest == null ? "作业或测试说明" : refTest.getRemark());
        //批阅状态
        test.setReadOver(1);

        //判断是否是线上方式
        if(0 == test.getMethod()){
            log.debug("作业的答题方式为线上:0 == test.getMethod()");

            //获取被引用资源的习题列表，并复制给新增加的作业
            List<TestExercise> exercises = testExerciseDao.getAllForNewTest(testId,test.getId());

            //新增作业/测试
            if(!testDao.addOnline(test)){
                log.debug("新增线上作业失败:!testDao.addOnline(test)");

                throw new ServiceException("作业或测试数据添加失败");
            }

            //判断习题集合是否为空
            if(!exercises.isEmpty()){
                log.debug("习题集合不为空:!exercises.isEmpty()");

                List<Long> exerciseIds = new ArrayList<>();

                log.debug("循环习题集合递增习题的热度:TestExercise testExercise : exercises");
                for (TestExercise testExercise : exercises){
                    testExercise.setTestId(test.getId());

                    //递增习题热度
                    exerciseCountDao.increment(testExercise.getExerciseId(),ExerciseCountDao.USE_TOTAL);

                    exerciseIds.add(testExercise.getExerciseId());
                }

                //批量插入习题集合
                testExerciseDao.batchAdd(exercises);

                //判断习题集合中是否含有主观题，若有设置作业为“待批阅”
                if(!exerciseDao.checkObjective(exerciseIds) && testDao.setHaveRead(test.getId()) == -1){
                    throw new ServiceException("设置作业已批阅状态失败");
                }
            }
        }else{//线下方式
            log.debug("作业的答题方式为线下");

            //判断被引用作业是否存在
            if(null != refTest){
                log.debug("获取到被引用作业的明细:null != refTest");

                test.setTestFileName(refTest.getTestFileName());
                test.setTestDownloadPath(refTest.getTestDownloadPath());
                test.setTestShowPath(refTest.getTestShowPath());
                test.setAnswerFileName(refTest.getAnswerFileName());
                test.setAnswerDownloadPath(refTest.getAnswerDownloadPath());
                test.setAnswerShowPath(refTest.getAnswerShowPath());
                test.setResourceId(refTest.getResourceId());
            }

            //新增作业/测试
            if(!testDao.addOffline(test)){
                log.debug("新增线下作业失败:!testDao.addOffline(test)");

                throw new ServiceException("作业或测试数据添加失败");
            }
        }

        //判断是否需要为作业添加考点
        if(!test.getKnowledgePoints().isEmpty()){
            log.debug("作业的知识点集合不为空:!test.getKnowledgePoints().isEmpty()");

            testKpDao.addKp(test);
        }
    }

    /**
     * 获取作业明细（编辑页面）
     * @param testId 作业编号
     * @return Test
     *
     * @author fanruiping
     * @since 2015-04-01
     */
    public Test detailForEdit(Long testId){
        //获取作业明细
        Test test = testDao.getDetail(testId);

        //判断是否获取到作业数据且是否属于当前用户
        if(test != null && webContext.getUserId().equals(test.getCreatorId())){
            log.debug("获取到指定作业的明细数据:test != null");

            Set<Long> kpIds = testKpDao.getKpIds(testId);

            //作业包含知识点
            if(!kpIds.isEmpty()){
                log.debug("知识点编号集合不为空:!kpIds.isEmpty()");

                test.setKnowledgePoints(kpDao.getAll(kpIds));
            }

            //判断作业是线上还是线下
            if(test.getMethod() == 0){
                log.debug("作业答题方式为线上:test.getMethod() == 0");

                //习题编号集合
                List<Long> exerciseIds = new ArrayList<>();

                //子习题集合
                List<TestExercise> sonExercises = new ArrayList<>();

                //获取作业包含习题
                List<TestExercise> exercises = testExerciseDao.getExercises(testId,true);

                log.debug("循环习题列表插入子题集合:TestExercise exercise : exercises");
                for (TestExercise exercise : exercises){
                    //判断是否是子题
                    if(exercise.getParentId() != 0){
                        log.debug("习题为子习题:exercise.getParentId() != 0");

                        sonExercises.add(exercise);
                    }

                    exerciseIds.add(exercise.getExerciseId());
                }

                //判断习题编号集合是否为空
                if(!exerciseIds.isEmpty()){
                    log.debug("习题编号集合不为空:!exerciseIds.isEmpty()");

                    List<TestExercise> exerciseList = new ArrayList<>();

                    //获取答案数
                    Map<Long,Integer> answerCount = exerciseService.getExerciseAnswerCount(exerciseIds);
                    //习题明细
                    Map<Long,Exercise> exerciseMap = exerciseService.getDetailByIds(exerciseIds);

                    //子题集合
                    Map<Long,List<TestExercise>> mapSonExercises = new HashMap<>();

                    log.debug("循环为子习题插入详细信息:TestExercise testExercise : sonExercises");
                    //循环为子习题插入信息
                    for (TestExercise testExercise : sonExercises){
                        testExercise.setTitle(exerciseMap.get(testExercise.getExerciseId()).getShowTopic());
                        testExercise.setDifficulty(exerciseMap.get(testExercise.getExerciseId()).getDifficulty());
                        testExercise.setType(exerciseMap.get(testExercise.getExerciseId()).getType());

                        if(mapSonExercises.containsKey(testExercise.getParentId())){
                            log.debug("子习题集合中包含指定父习题:mapSonExercises.containsKey(testExercise.getParentId())");

                            mapSonExercises.get(testExercise.getParentId()).add(testExercise);
                        }else{
                            log.debug("子习题集合中不包含指定父习题");

                            List<TestExercise> testExerciseList = new ArrayList<>();
                            testExerciseList.add(testExercise);

                            mapSonExercises.put(testExercise.getParentId(),testExerciseList);
                        }
                    }

                    log.debug("循环习题集合插入详细信息和子习题集合:TestExercise exercise : exercises");
                    //循环插入习题信息和子题集合
                    for(TestExercise exercise : exercises){
                        //判断习题是否是父习题
                        if(exercise.getParentId() == 0){
                            log.debug("习题是父习题:exercise.getParentId() == 0");

                            exercise.setTitle(exerciseMap.get(exercise.getExerciseId()).getShowTopic());
                            exercise.setDifficulty(exerciseMap.get(exercise.getExerciseId()).getDifficulty());
                            exercise.setType(exerciseMap.get(exercise.getExerciseId()).getType());

                            //插入答案数
                            if(answerCount != null && answerCount.containsKey(exercise.getExerciseId())){
                                log.debug("习题填空数集合中包含指定习题信息:answerCount != null && answerCount.containsKey(exercise.getExerciseId())");

                                exercise.setBlankNum(answerCount.get(exercise.getExerciseId()));
                            }

                            //插入子题集合
                            exercise.setCrossheads(mapSonExercises.get(exercise.getExerciseId()));

                            exerciseList.add(exercise);
                        }
                    }

                    test.setExercises(exerciseList);
                }
            }else if(test.getMethod() == 1 && test.getResourceId() > 0){//作业是线下的且引用了资源
                log.debug("作业答题方式为线下且引用了资源信息");

                test.setResource(resourceDao.getById(test.getResourceId()));
            }
        }else {
            throw new ServiceException("请求的数据不存在");
        }

        return test;
    }

    /**
     * 获取线上作业明细
     * @param testId 作业编号
     * @return Test
     *
     * @author fanruiping
     * @since 2015-03-26
     */
    public Test onlineDetail(Long testId){
        //获取作业明细
        Test test = testDao.getDetail(testId);

        //判断是否获取到作业数据
        if(test != null){
            log.debug("获取到指定作业的详细信息:test != null");

            //获取专业名称
            Specialty specialty = specialtyDao.getById(test.getSpecId());
            test.setSpecName(specialty != null ? specialty.getName() : "");

            //获取课程群组名称
            CourseGroup courseGroup = courseGroupDao.getById(test.getGroupId());
            test.setGroupName(courseGroup != null ? courseGroup.getName() : "");

            //获取作业包含习题
            test.setExercises(testExerciseDao.getExercises(testId,false));
        }

        return test;
    }

    /**
     * 获取线下作业明细
     * @param testId 作业编号
     * @return Test
     *
     * @author fanruiping
     * @since 2015-03-26
     */
    public Test offlineDetail(Long testId,User user){
        //获取作业明细
        Test test = testDao.getDetail(testId);

        //判断是否获取到作业明细
        if(test != null){
            log.debug("获取到作业的详细信息:test != null");

            //获取当前时间
            Date currentTime = new Date(new java.util.Date().getTime());

            //当前用户是学生，且还没到结束时间时，不返回答案附件信息
            if(user.getRole() == Roles.STUDENT && currentTime.before(test.getEndTime())){
                log.debug("当前登录用户为学生且未到结束时间:user.getRole() == Roles.STUDENT && currentTime.before(test.getEndTime())");

                test.setAnswerDownloadPath("");
                test.setAnswerShowPath("");
                test.setAnswerFileName("");
            }

            //获取专业名称php
            Specialty specialty = specialtyDao.getById(test.getSpecId());
            test.setSpecName(specialty != null ? specialty.getName() : "");

            //获取课程群组名称
            CourseGroup courseGroup = courseGroupDao.getById(test.getGroupId());
            test.setGroupName(courseGroup != null ? courseGroup.getName() : "");

            Set<Long> kpIds = testKpDao.getKpIds(testId);

            if(!kpIds.isEmpty()){
                log.debug("作业知识点编号集合不为空:!kpIds.isEmpty()");

                test.setKnowledgePoints(kpDao.getAll(kpIds));
            }

            return test;
        }else{
            throw new ServiceException("请求的数据不存在");
        }
    }

    /**
     * 更新作业或测试
     * @param test 作业或测试实体
     *
     * @author fanruiping
     * @since 2015-03-27
     */
    @Transactional
    public void update(Test test) {
        //获取当前时间
        Date currentTime = new Date(new java.util.Date().getTime());

        //获取作业明细
        Test testDetail = testDao.getDetail(test.getId());

        //判断作业测试是否存在
        if (null == testDetail) {
            log.debug("指定作业数据不存在:null == test");

            throw new ServiceException("该作业或测试不存在");
        }

        //判断作业所属人
        if(!webContext.getUserId().equals(testDetail.getCreatorId())){
            throw new ServiceException("当前用户不是该数据的创建人，不能编辑改作业");
        }

        //判断当前时间大于或等于作业开始时间
        if (currentTime.after(testDetail.getStartTime()) || currentTime.equals(testDetail.getStartTime())) {
            log.debug("作业已开始:currentTime.after(testDetail.getStartTime()) || currentTime.equals(testDetail.getStartTime())");

            throw new ServiceException("作业或测试已开始，不能编辑");
        }

        //判断作业是线上的，设置资源编号为0
        if(test.getMethod() == 0 && test.getResourceId() == null){
            log.debug("作业答题方式为线上且为引用资源信息:test.getMethod() == 0 && test.getResourceId() == null");

            test.setResourceId(Long.valueOf(0));
        }

        //判断是否是线上，删除该作业的所有习题，并新增
        if (test.getMethod() == 0 && testExerciseDao.deleteAllById(test.getId()) >= 0) {
            log.debug("作业答题方式为线上且删除作业所有习题成功");

            //判断作业下有习题
            if(!test.getExercises().isEmpty()){
                log.debug("作业包含习题集合不为空:!test.getExercises().isEmpty()");

                List<Long> exerciseIds = new ArrayList<>();

                log.debug("循环作业习题集合插入作业编号、递增习题热度");
                //循环插入作业/测试编号
                for(TestExercise testExercise : test.getExercises()){

                    testExercise.setTestId(test.getId());

                    //递增习题热度
                    exerciseCountDao.increment(testExercise.getExerciseId(),ExerciseCountDao.USE_TOTAL);
                }

                //批量添加作业习题关系
                testExerciseDao.batchAdd(test.getExercises());

                //判断是否含有主观题
                if(exerciseDao.checkObjective(exerciseIds)){
                    test.setReadOver(0);
                }
            }
        }else {
            //判断是否更换作业原件
            if(test.getTestDownloadPath().isEmpty() || (testDetail.getTestDownloadPath() == test.getTestDownloadPath())){
                test.setTestShowPath("");
            }

            //判断是否更换作业答案原件
            if(test.getAnswerDownloadPath().isEmpty() || (testDetail.getAnswerDownloadPath() == test.getAnswerDownloadPath())){
                test.setAnswerShowPath("");
            }
        }

        //判断是否是引用资源
        if(test.getResourceId() != null){
            log.debug("作业引用了资源信息:test.getResourceId() != null");

            //获取资源明细
            Resource resource = resourceDao.getFileDetail(test.getResourceId());
            //判断是否获取到资源明细，若有插入附件信息
            if(null != resource){
                log.debug("获取到引用资源的信息:null != resource");

                test.setTestFileName(resource.getOrigin());
                test.setTestDownloadPath(resource.getDownloadUrl());
                test.setTestShowPath(resource.getShowAddr());
            }
        }

        //判断是否更新成功
        if (testDao.update(test) == -1) {
            log.debug("更新作业信息失败:testDao.update(test) == -1");

            throw new ServiceException("作业或测试数据更新失败");
        }

        //删除已有知识点，并新增
        if (!test.getKnowledgePoints().isEmpty() && testKpDao.deleteKps(test.getId())) {
            log.debug("作业包含知识点集合不为空且删除已有知识点集合成功");

            testKpDao.addKp(test);
        }
    }
    /**
     * 保存学生上传的作业文件
     *
     * @param studentTest 学生作业信息
     *
     * @author cx
     * @since 2015/3/20
     */
    public void saveOfflineTest(final StudentTest studentTest) {
        //检查作业是否属于登录学生
        checkStudentTest(studentTest.getTestId());
        //判断作业是否已提交
        checkTestSubmitted(studentTest.getTestId());
        //检查作业时间有效性
        checkTestTime(studentTest.getTestId());

        long studentId = webContext.getUserId();
        //获取学生作业的文件信息
        StudentTest stFileInfo = testDao.getSTFileInfo(studentId, studentTest.getTestId());

        int result;
        if (stFileInfo == null) { //新增学生线上作业
            log.debug("学生作业文件信息为null:stFileInfo == null");

            studentTest.setStudentId(studentId);
            result = testDao.saveSTFileInfo(studentTest);
        }
        else { //更新学生线上作业
            log.debug("学生作业文件信息不为null");

            stFileInfo.setSubmittedFileName(studentTest.getSubmittedFileName());
            stFileInfo.setSubmittedDownPath(studentTest.getSubmittedDownPath());
            result = testDao.updateSTFileInfo(stFileInfo);
        }

        try {
            if (result == 1) {
                log.debug("转换学生上传作业文件:result == 1");

                //转换文件
                fileConverter.newTask("studentTest:"+ FileNameCode.studentTestMap.get(1) +":"+ studentTest.getId());
            }
        } catch (IOException ex) {
            log.debug("文件转换异常");
            log.warn("文件转换IO异常", ex);

            throw new ServiceException("文件转换异常");
        }
    }

    /**
     * 检查作业的开始结束时间
     *
     * @param testId 作业ID
     *
     * @author cx
     * @since 2015/3/31
     */
    public void checkTestTime(final long testId) {
        //获取作业构建信息
        Test test = testDao.getBuildInfo(testId);

        if (test == null) {
            log.debug("不存在的作业:test == null");
            log.warn("不存在的作业:testId->{}", testId);

            throw new ServiceException("操作数据有误");
        }

        if (isBeforeTime(test)) {
            log.debug("作业未开始:isOverTime(test)");
            log.warn("作业未开始:testId->{}", testId);

            throw new ServiceException("作业未开始");
        }

        if (isOverTime(test)) {
            log.debug("作业已结束:isOverTime(test)");
            log.warn("作业已结束:testId->{}", testId);

            throw new ServiceException("作业已结束");
        }
    }

    /**
     * 检查作业是否属于登录学生
     *
     * @param testId 作业ID
     *
     * @author cx
     * @since 2015/3/21
     */
    public void checkStudentTest(final long testId) {
        if (!testDao.hasTestForStudent(testId, webContext.getAccount())) {
            log.debug("学生操作不属于自己的作业:!testDao.hasTestForStudent(testId, webContext.getAccount())");
            log.warn("学生操作不属于自己的作业:testId->{},studentNum->{}", testId, webContext.getAccount());

            throw new ServiceException("操作数据有误");
        }
    }

    /**
     * 检查习题是否属于指定作业
     *
     * @param testId 作业ID
     * @param exerciseId 习题ID
     * @author cx
     * @since 2015/3/30
     */
    public void checkTestExercise(final long testId, final long exerciseId) {
        if (!testDao.hasExerciseForTest(testId, exerciseId)) {
            log.debug("学生操作不属于自己的作业习题:!testDao.hasExerciseForTest(testId, exerciseId)");
            log.warn("学生操作不属于自己的作业习题:testId->{},exerciseId->{}", testId, exerciseId);

            throw new ServiceException("操作数据有误");
        }
    }

    /**
     * 作业是否超过结束时间
     *
     * @param test 作业类
     * @return boolean
     *
     * @author cx
     * @since 2015/4/3
     */
    private boolean isOverTime(final Test test) {
        //获取当前时间
        Date currentTime = new Date(new java.util.Date().getTime());

        log.debug("作业超过结束时间");
        return currentTime.after(test.getEndTime());
    }

    /**
     * 作业是否开始
     * @param test 作业实体类
     * @return boolean
     * 
     * @author cx
     * @since 2015/4/22
     */
    private boolean isBeforeTime(final Test test) {
        //获取当前时间
        Date currentTime = new Date(new java.util.Date().getTime());

        log.debug("作业未到开始时间");
        return currentTime.before(test.getStartTime());
    }

    /**
     * 检查学生作业是否提交
     *
     * @param testId 作业ID
     *
     * @author cx
     * @since 2015/3/31
     */
    public void checkTestSubmitted(final long testId) {
        //获取作业的提交信息
        StudentTest studentTest = testDao.getStudentTest(testId, webContext.getUserId());

        if (studentTest != null && studentTest.getStatus() == 1) {
            log.debug("作业已被提交:studentTest != null && studentTest.getStatus() == 1");
            log.warn("作业已提交:testId->{},studentId->{}", testId, webContext.getUserId());

            throw new ServiceException("作业已提交");
        }
    }

    /**
     * 删除作业或测试
     * @param testId 作业或测试编号
     *
     * @author fanruiping
     * @since 2015-03-27
     */
    @Transactional
    public void delete(Long testId) {
        Test test = testDao.getDetail(testId);

        //判断作业测试是否存在
        if (null == test) {
            log.debug("指定作业数据不存在:null == test");

            throw new ServiceException("该作业或测试不存在");
        }

        //判断作业所属人
        if(!webContext.getUserId().equals(test.getCreatorId())){
            throw new ServiceException("当前用户不是该作业的创建人，不能删除该作业");
        }

        //获取当前时间
        Date currentTime = new Date(new java.util.Date().getTime());

        //判断当前时间大于或等于作业开始事件
        if (currentTime.after(test.getStartTime()) || currentTime.equals(test.getStartTime())) {
            log.debug("作业已开始:currentTime.after(test.getStartTime()) || currentTime.equals(test.getStartTime())");

            throw new ServiceException("作业或测试已开始，不能删除");
        }

        //删除作业或测试的知识点
        if (!testKpDao.deleteKps(testId)) {
            log.debug("删除作业包含知识点集合失败:!testKpDao.deleteKps(testId)");

            throw new ServiceException("作业或测试包含知识点删除失败");
        }

        //删除作业或测试包含的习题
        if (testExerciseDao.deleteAllById(testId) == -1) {
            log.debug("删除作业包含习题集合失败:testExerciseDao.deleteAllById(testId) == -1");

            throw new ServiceException("作业或测试包含习题删除失败");
        }

        //删除作业或测试
        if (testDao.delete(testId) == -1) {
            log.debug("删除作业数据失败:testDao.delete(testId) == -1");

            throw new ServiceException("作业或测试数据删除失败");
        }
    }

    /**
     * 删除学生未提交的线下作业
     *
     * @param testId 作业ID
     *
     * @author cx
     * @since 2015/3/21
     */
    public void deleteTestFile(final long testId) {
        //检查作业是否属于登录学生
        checkStudentTest(testId);
        //判断作业是否已提交
        checkTestSubmitted(testId);
        //检查是否已超过作业结束时间
        checkTestTime(testId);

        log.debug("删除学生未提交的线下作业");
        testDao.deleteTestFile(webContext.getUserId(), testId);
    }

    /**
     * 更新学生作业为已提交
     *
     * @param testId 作业ID
     *
     * @author cx
     * @since 2015/3/21
     */
    public void submitOfflineTest(final long testId) {
        //检查作业是否属于登录学生
        checkStudentTest(testId);
        //检查是否已超过作业结束时间
        checkTestTime(testId);

        //获取学生作业记录
        StudentTest studentTest = testDao.getStudentTest(testId, webContext.getUserId());

        if (studentTest == null) {
            log.debug("学生作业记录为null:studentTest == null");

            studentTest = new StudentTest(webContext.getUserId(), testId, 0, StudentTestStatus.BE_SUBMITED, 0, 0, StudentTestStatus.NOT_READ_OVER);
            testDao.saveStudentTest(studentTest);
        }
        else {
            log.debug("学生作业记录不为null");

            studentTest.setStatus(StudentTestStatus.BE_SUBMITED); //已提交
            testDao.updateStudentTest(studentTest);
        }

        log.debug("递增作业提交次数");
        //递增作业提交次数
        testSubmittedCounterDao.incrSubmittedNum(testId);
    }

    /**
     * 获取指定作业下的学生及批阅状态
     * @param testId 作业编号
     * @return List
     *
     * @author fanruiping
     * @since 2015-03-31
     */
    public List<StudentTest> getStudentsByTestId(Long testId){
        List<StudentTest> studentTests = studentTestDao.getStudentsByTest(testId);

        //学生作业集合不为空
        if(!studentTests.isEmpty()){
            log.debug("学生作业集合不为空:!studentTests.isEmpty()");

            Set<Long> studentIds = new HashSet<>();

            log.debug("循环学生作业集合获取学生编号集合:StudentTest studentTest : studentTests");
            //循环获取学生编号
            for(StudentTest studentTest : studentTests){
                studentIds.add(studentTest.getStudentId());
            }

            Map<Long,Student> studentMap = studentService.getPictures(studentIds);

            log.debug("循环学生作业集合插入学生名称:StudentTest studentTest : studentTests");
            //循环插入学生姓名
            for(StudentTest studentTest : studentTests){
                studentTest.setStudentName(studentMap.get(studentTest.getStudentId()).getName());
            }
        }

        return studentTests;
    }

    /**
     * 作业明细（统计）
     * @param testId 作业编号
     * @return Test
     *
     * @author fanruiping
     * @since 2015-03-26
     */
    public Test statisticsDetail(Long testId){
        //获取作业明细
        Test test = testDao.getDetail(testId);

        //判断作业创建人
        if(null != test && !test.getCreatorId().equals(webContext.getUserId())){
            log.debug("作业创建人不是当前用户");

            throw new ServiceException("当前用户无权查看该作业明细");
        }

        //判断是否获取到作业数据
        if(test != null){
            log.debug("获取到指定作业详细数据:test != null");

            //获取专业名称
            Specialty specialty = specialtyDao.getById(test.getSpecId());
            test.setSpecName(specialty != null ? specialty.getName() : "");

            //获取课程群组名称
            CourseGroup courseGroup = courseGroupDao.getById(test.getGroupId());
            test.setGroupName(courseGroup != null ? courseGroup.getName() : "");

            //获取作业包含习题
            test.setExercises(testExerciseDao.getExercises(testId,false));

            //作业提交人数
            test.setSubmittedNum((int) testSubmittedCounterDao.getSubmittedNum(test.getId()));
        }else {
            throw new ServiceException("请求的数据不存在");
        }

        return test;
    }

    /**
     * 指定作业下指定习题的学生答题情况
     * @param testId 作业编号
     * @param exerciseId 习题编号
     * @return List
     *
     * @author fanruiping
     * @since 2015-04-01
     */
    public List<StudentAnswer> getTestExerciseStatistics(Long testId,Long exerciseId){
        List<Long> allStudentIds = studentTestDao.getStudentIds(testId);

        List<StudentAnswer> studentAnswers = studentAnswerRedisDao.getStudentAnswers(allStudentIds, testId, exerciseId);

        log.debug("循环学生答题集合设置学生答题状态:StudentAnswer studentAnswer : studentAnswers");
        //循环获取提交该习题的学生编号集合
        for(StudentAnswer studentAnswer : studentAnswers){
            if(studentAnswer.getStatus() == 1 || (studentAnswer.getStatus() == 4 && studentAnswer.getScore() > 0)){
                log.debug("学生状态为正确或主观题已批阅:studentAnswer.getStatus() == 1 || studentAnswer.getStatus() == 4");

                studentAnswer.setStatus(1);
            }else {
                log.debug("学生状态为未答、错误或主观题未批阅");

                studentAnswer.setStatus(0);
            }
        }

        return studentAnswers;
    }

    /**
     * 学生答题集合
     * @param testId 作业编号
     * @param studentId 学生编号
     * @return List
     *
     * @author fanruiping
     * @since 2015-04-02
     */
    public List<StudentAnswer> getExerciseScoreByStudentId(Long testId,Long studentId){
        //获取作业明细
        Test testDetail = testDao.getDetail(testId);

        //判断作业是否存在
        if(null == testDetail){
            log.debug("未获取到指定作业数据:null == testDetail");

            throw new ServiceException("该作业不存在");
        }

        //获取作业包含的习题
        List<TestExercise> exerciseList = testExerciseDao.getExercises(testId,false);

        //指定学生答题分值
        List<StudentAnswer> studentAnswers = studentAnswerRedisDao.getStudentAnswers(studentId, exerciseList);

        //存储习题初始分值
        Map<Long,Integer> exerciseScore = new HashMap<>();

        log.debug("循环作业习题集合插入习题初始分值:TestExercise testExercise : exerciseList");
        //循环获取习题初始分值
        for(TestExercise testExercise : exerciseList){
            exerciseScore.put(testExercise.getExerciseId(), testExercise.getScore());
        }

        //获取当前时间
        Date currentTime = new Date(new java.util.Date().getTime());

        //存储学生已答习题编号集合
        Set<Long> ids = new HashSet<>();

        log.debug("循环学生答题集合插入习题初始分值:StudentAnswer studentAnswer : studentAnswers");
        //循环插入习题初始分值
        for (StudentAnswer studentAnswer : studentAnswers){
            //判断习题分值集合中是否包含指定习题信息
            if(exerciseScore.containsKey(studentAnswer.getExerciseId())){
                log.debug("习题分值集合中包含指定习题信息:exerciseScore.containsKey(studentAnswer.getExerciseId())");

                studentAnswer.setDefineScore(exerciseScore.get(studentAnswer.getExerciseId()));
            }

            //判断若登录用户为学生且未达到结束时间，则不传递分值
            if(webContext.getRole() == Roles.STUDENT && currentTime.before(testDetail.getEndTime())){
                log.debug("当前登录用户为学生且作业未到达结束时间");

                studentAnswer.setScore(null);
                studentAnswer.setStatus(null);
            }

            ids.add(studentAnswer.getExerciseId());
        }

        return studentAnswers;
    }

    /**
     * 获取学生作业详细
     * @param testId 作业编号
     * @param studentId 学生编号
     * @param entity 登录用户信息
     * @return StudentTest
     *
     * @author fanruiping
     * @since 2015-04-02
     */
    public StudentTest getStudentTestDetail(Long testId, Long studentId,User entity){
        //获取作业明细
        Test test = testDao.getDetail(testId);

        //判断作业是否存在
        if(null == test){
            log.debug("为获取指定作业信息:null == test");

            throw new ServiceException("该作业不存在");
        }

        //获取学生作业信息
        StudentTest studentTest = studentTestDao.getDetailByStudentId(testId,studentId);

        //获取当前时间
        Date currentTime = new Date(new java.util.Date().getTime());

        //当前用户是学生，且还没到结束时间时，不返回批阅文件信息和总分值
        if(test != null && entity.getRole() == Roles.STUDENT && currentTime.before(test.getEndTime())){
            log.debug("作业存在且登录用户角色是学生且未到达结束时间");

            studentTest.setScore(null);

            studentTest.setCorrectedFileName("");
            studentTest.setCorrectShowPath("");
            studentTest.setCorrectedDownPath("");
        }

        return studentTest;
    }

    /**
     * 批阅主观题
     * @param studentAnswer 学生作业实体
     * @return int
     *
     * @author fanruiping
     * @since 2015-04-02
     */
    @Transactional
    public int readOverSubjective(StudentAnswer studentAnswer){
        //判断当前用户角色
        if(webContext.getRole() == Roles.STUDENT){
            log.debug("当前登录用户是学生:webContext.getRole() == Roles.STUDENT");

            throw new ServiceException("学生不能批阅主观题");
        }

        //验证作业是否属于当前教师
        checkTeacherHasTest(webContext.getUserId(),studentAnswer.getTestId());

        //学生作业批阅完成
        int hasReadOver = 0;

        //习题编号
        Long exerciseId = studentAnswer.getExerciseId();

        //当前分值
        int currentScore = studentAnswer.getScore();

        //应加分值（若是重复批阅，需要处理分值）
        int score = studentAnswer.getScore();

        //获取学生答题明细
        StudentAnswer answerDetail = studentAnswerRedisDao.getStudentAnswer(studentAnswer.getStudentId(),studentAnswer.getTestId(),exerciseId);

        //判断学生答题是否存在
        if(null == answerDetail){
            log.debug("未获取到学生作业指定习题的答题明细:null == answerDetail");

            throw new ServiceException("习题明细不存在");
        }else{
            log.debug("获取到学生作业指定习题的答题明细");

            //判断是否已批阅过，若已批阅过，则应加分值需要减去已有分值
            if(4 == answerDetail.getStatus()){
                log.debug("答题状态为主观题已批阅:4 == answerDetail.getStatus()");

                score -= answerDetail.getScore();
            }
        }

        //判断是否是父习题
        if(studentAnswer.getParentId() != 0){
            log.debug("习题是子习题:studentAnswer.getParentId() != 0");

            //父习题编号
            exerciseId = studentAnswer.getParentId();

            //获取父习题明细
            StudentAnswer parentDetail = studentAnswerRedisDao.getStudentAnswer(studentAnswer.getStudentId(),studentAnswer.getTestId(),exerciseId);

            //判断习题是否存在
            if(null == parentDetail){
                log.debug("未获取到父习题的明细信息:null == parentDetail");

                throw new ServiceException("习题不存在");
            }

            //获取父习题当前分值
            currentScore = parentDetail.getScore()+score;

            //父习题答题实体
            StudentAnswer parentAnswer = new StudentAnswer(studentAnswer.getStudentId(),studentAnswer.getTestId(),exerciseId);
            parentAnswer.setScore(currentScore);
            parentAnswer.setStatus(4);

            //更新父习题分值及状态
            studentAnswerRedisDao.updateForReadover(parentAnswer);
        }

        //获取习题初始分值
        int defineScore = testExerciseDao.getScore(studentAnswer.getTestId(),exerciseId);

        //学生得分等于初始分值，则递增习题正确率
        if(defineScore == currentScore){
            log.debug("学生得分等于习题初始分值:defineScore == currentScore");

            //递增习题正确率
            exerciseCountDao.increment(exerciseId,ExerciseCountDao.RIGHT_TOTAL);
        }

        //更新作业总分，更新批阅结果
        if(studentTestDao.updateScore(studentAnswer.getStudentId(),studentAnswer.getTestId(),score) == 1){
            log.debug("更新学生作业得分:studentTestDao.updateScore(studentAnswer.getStudentId(),studentAnswer.getTestId(),score) == 1");

            //设置学生答题状态为已批阅
            studentAnswer.setStatus(4);

            studentAnswerRedisDao.updateForReadover(studentAnswer);
        }else{
            log.debug("更新学生作业得分失败");

            throw new ServiceException("习题批阅失败");
        }

        //判断当前学生作业系统是否是主观未批阅，若是则递减学生作业主观题数量
        if(3 == answerDetail.getStatus() && studentTestDao.decrSubjectiveCount(studentAnswer.getStudentId(),studentAnswer.getTestId()) == -1){
            //递减学生作业的主观题数量
            log.debug("递减待批阅主观题数量失败");

            throw new ServiceException("习题批阅失败");
        }

        //判断学生作业是否已完全批阅完成
        if(studentTestDao.getSubjectiveCount(studentAnswer.getStudentId(), studentAnswer.getTestId()) == 0){
            log.debug("习题答题状态为主观未批阅且指定学生的作业已批阅完成");

            hasReadOver = 1;

            //设置显示作业为已批阅
            if(studentTestDao.setOnlineReadOver(studentAnswer.getStudentId(),studentAnswer.getTestId()) == -1){
                log.debug("设置批阅状态失败");

                throw new ServiceException("习题批阅失败");
            }
        }

        //判断作业是否已批阅完成
        if(studentTestDao.getSubjectiveCount(studentAnswer.getTestId()) == 0 && testDao.setHaveRead(studentAnswer.getTestId()) == -1){
            log.debug("获取待批阅主观题数量为0且设置作业已批阅状态失败");

            throw new ServiceException("习题批阅失败");
        }

        return hasReadOver;
    }

    /**
     * 批阅学生线下作业
     * @param studentTest 学生作业实体
     *
     * @author fanruiping
     * @since 2015-04-02
     */
    public void readOverOfflineTest(StudentTest studentTest){
        //判断当前用户角色
        if(webContext.getRole() == Roles.STUDENT){
            log.debug("当前登录用户角色为学生:webContext.getRole() == Roles.STUDENT");

            throw new ServiceException("学生不能批阅线下作业");
        }

        //验证作业是否属于当前教师
        checkTeacherHasTest(webContext.getUserId(),studentTest.getTestId());

        //更新批阅结果
        if(studentTestDao.readOverOffline(studentTest) == -1){
            log.debug("批阅学生线下作业失败:studentTestDao.readOverOffline(studentTest) != 1");

            throw new ServiceException("批阅线下作业失败");
        }

        //判断学生作业已全部批阅完成，设置作业已批阅状态失败
        if(studentTestDao.offlineHasReadOver(studentTest.getTestId()) && testDao.setHaveRead(studentTest.getTestId()) == -1){
            log.debug("学生作业已全部批阅完成，设置作业已批阅状态失败");

            throw new ServiceException("设置作业已批阅状态失败");
        }
    }

    /**
     * 获取学生最后答题的习题ID
     *
     * @param testId 作业ID
     * @return 习题ID
     *
     * @author cx
     * @since 2015/3/23
     */
    public long getLastExercisePosition(final long testId) {
        StudentTest studentTest = testDao.getStudentTest(testId, webContext.getUserId());

        if (studentTest == null) {
            log.debug("学生作业为null:studentTest == null");
            return 0;
        }

        log.debug("获取学生最后答题的习题ID");
        return studentTest.getLastPosition();
    }

    /**
     * 获取作业习题
     *
     * @param testId 作业ID
     * @return 作业习题集合
     *
     * @author cx
     * @since 2015/3/23
     */
    public List<TestExercise> getTestExercises(final long testId) {
        //检查作业是否属于登录学生
        checkStudentTest(testId);

        List<TestExercise> testExercises = exerciseDao.getTestExercises(testId);
        //获取登录学生对作业习题的答题状态
        studentAnswerRedisDao.setStudentAnswerStatus(webContext.getUserId(), testExercises);

        log.debug("获取作业习题");
        return testExercises;
    }

    /**
     * 保存学生答题记录
     *
     * @param studentAnswer 学生答题记录
     *
     * @author cx
     * @since 2015/3/31
     */
    @Transactional
    public void saveStudentAnswer(StudentAnswer studentAnswer) {
        long testId = studentAnswer.getTestId();
        long exerciseId = studentAnswer.getExerciseId();
        long studentId = webContext.getUserId();

        //检查作业是否属于登录学生
        checkStudentTest(testId);
        //检查习题是否属于作业
        checkTestExercise(testId, exerciseId);
        //判断作业是否已提交
        checkTestSubmitted(testId);
        //检查是否已超过作业结束时间
        checkTestTime(testId);

        //判断习题的对错
        studentAnswerService.handleStudentAnswer(studentAnswer);

        //记录答题位置
        updateLastPosition(testId, exerciseId);

        //保存学生答题的得分、状态和内容
        studentAnswer.setStudentId(studentId);
        List<StudentAnswer> sonStudentAnswers = studentAnswer.getSonStudentAnswers();

        List<StudentAnswer> studentAnswers = new ArrayList<>();
        studentAnswers.add(studentAnswer);

        if (sonStudentAnswers != null) {
            log.debug("循环学生对子习题的答题");
            for (StudentAnswer sonStudentAnswer : sonStudentAnswers) {
                sonStudentAnswer.setStudentId(studentId);
                studentAnswers.add(sonStudentAnswer);
            }
        }

        //批量保存学生答题记录
        studentAnswerRedisDao.saveStudentAnswers(studentAnswers);
    }

    /**
     * 记录最后答题的习题ID
     * @param testId 作业ID
     * @param exerciseId 最后位置的习题ID
     *
     * @author cx
     * @since 2015/3/31
     */
    public void updateLastPosition(final long testId, final long exerciseId) {
        long studentId = webContext.getUserId();

        //获取学生作业记录
        StudentTest studentTest = testDao.getStudentTest(testId, studentId);

        if (studentTest == null) {
            log.debug("新增学生答题位置:studentTest == null");

            studentTest = new StudentTest(studentId, testId, 0, StudentTestStatus.NOT_SUBMITED, exerciseId, 0, StudentTestStatus.NOT_READ_OVER);
            testDao.saveStudentTest(studentTest);
        }
        else {
            log.debug("更新最后答题位置");

            studentTest.setLastPosition(exerciseId);
            testDao.updateStudentTest(studentTest);
        }
    }

    /**
     * 获取作业的习题信息
     *
     * @param exerciseId 习题ID
     * @return TestExercise
     *
     * @author cx
     * @since 2015/3/27
     */
    public TestExercise getExerciseOfTest(final long testId, final long exerciseId) {
        //获取作业习题的信息
        TestExercise testExercise = exerciseDao.getTestExercise(testId, exerciseId);

        if (testExercise == null) {
            log.debug("作业习题信息为null:testExercise == null");
            log.warn("作业习题不存在：exerciseId->{}", exerciseId);

            throw new ServiceException("作业习题不存在");
        }

        log.debug("获取作业的习题信息");
        return testExercise;
    }

    /**
     * 提交学生线上作业
     *
     * @param testId 作业ID
     *
     * @author cx
     * @since 2015/3/31
     */
    public void submitOnlineTest(final long testId) {
        //检查作业是否属于登录学生
        checkStudentTest(testId);
        //检查是否已超过作业结束时间
        checkTestTime(testId);

        //获取学生作业记录
        StudentTest studentTest = testDao.getStudentTest(testId, webContext.getUserId());
        if (studentTest == null || studentTest.getStatus() != StudentTestStatus.NOT_SUBMITED) {
            log.debug("studentTest == null || studentTest.getStatus() != StudentTestStatus.NOT_SUBMITED");
            throw new ServiceException("学生作业记录出错");
        }

        //作业得分
        int testScore = 0;
        //主观未评判题量
        int subjectiveCnt = 0;

        //获取指定作业的习题集合（不包括子习题）
        List<TestExercise> testExercises = exerciseDao.getTestExercises(testId);
        //获取学生的作业答题信息（不包括子习题）
        List<StudentAnswer> studentAnswers = studentAnswerRedisDao.getStudentAnswers(webContext.getUserId(), testExercises);

        List<Long> answeredExerciseIds = Lists.newArrayList(); //答过的习题ID集合
        List<Long> rightExerciseIds = Lists.newArrayList(); //答对的习题ID集合

        log.debug("循环学生回答");
        for (StudentAnswer studentAnswer : studentAnswers) {
            //累计答题得分
            testScore += studentAnswer.getScore();
            answeredExerciseIds.add(studentAnswer.getExerciseId());

            if (studentAnswer.getStatus() == StudentAnswerStatus.RIGHT_ANSWER) {
                log.debug("学生答题正确:studentAnswer.getStatus() == StudentAnswerStatus.RIGHT_ANSWER");
                rightExerciseIds.add(studentAnswer.getExerciseId());
            }
            else if (studentAnswer.getStatus() == StudentAnswerStatus.SUBJECTIVE_NO_VIEW) {  //累计主观未评判题量
                log.debug("累计主观未评判题量:studentAnswer.getStatus() == StudentAnswerStatus.SUBJECTIVE_NO_VIEW");
                subjectiveCnt++;
            }
        }

        studentTest.setStatus(StudentTestStatus.BE_SUBMITED); //已提交
        studentTest.setScore(testScore);
        studentTest.setSubjectiveCnt(subjectiveCnt);
        if (subjectiveCnt == 0) {
            log.debug("没有主观题批阅状态为已批阅:subjectiveCnt == 0");
            studentTest.setReadOver(StudentTestStatus.READ_OVER);
        }
        //更新作业状态为已提交，记录作业主观未评判题量
        testDao.updateStudentTest(studentTest);

        //批量递增习题的回答次数
        exerciseCountDao.incrByBatch(answeredExerciseIds, ExerciseCountDao.ANSWER_TOTAL);
        //批量递增习题的正确回答总数
        exerciseCountDao.incrByBatch(rightExerciseIds, ExerciseCountDao.RIGHT_TOTAL);
        //递增作业的提交次数
        testSubmittedCounterDao.incrSubmittedNum(testId);
    }

    /**
     * 获取作业明细及所有学生及分值
     * @param testId 作业编号
     * @return Test
     *
     * @author fanruiping
     * @since 2015-04-03
     */
    public Test getAllStudentScore(Long testId){
        //获取作业明细
        Test test = testDao.getDetail(testId);

        //获取当前时间
        Date currentTime = new Date(new java.util.Date().getTime());

        //是否获取到作业明细
        if(test != null){
            log.debug("获取指定作业的详细信息");

            //作业提交人数
            test.setSubmittedNum((int) testSubmittedCounterDao.getSubmittedNum(test.getId()));

            //获取指定作业下的所有学生及分值
            List<StudentTest> studentTests = studentTestDao.getStudentsByTest(testId);

            //判断学生作业集合是否为空
            if(!studentTests.isEmpty()){
                log.debug("学生作业集合不为空:!studentTests.isEmpty()");

                Set<Long> studentIds = new HashSet<>();

                log.debug("循环学生作业集合获取学生编号集合:StudentTest studentTest : studentTests");
                //循环获取学生编号
                for(StudentTest studentTest : studentTests){
                    studentIds.add(studentTest.getStudentId());
                }

                Map<Long,Student> studentMap = studentService.getPictures(studentIds);

                log.debug("循环学生作业集合插入学生姓名:StudentTest studentTest : studentTests");
                //循环插入学生姓名
                for(StudentTest studentTest : studentTests){
                    studentTest.setStudentName(studentMap.get(studentTest.getStudentId()).getName());
                }

                test.setStudentTests(studentTests);
            }
        }

        return test;
    }

    /**
     * 上传批阅文件
     * @param testId 作业编号
     * @param studentId 学生编号
     * @param correctFileName 文件名
     * @param fileUrl 文件地址
     *
     * @author fanruiping
     * @since 2015-04-06
     */
    public void updateReadOverFile(Long testId,Long studentId,String correctFileName,String fileUrl){
        //更新学生作业批阅文件是否成功
        if(studentTestDao.updateReadOverFile(testId, studentId, correctFileName, fileUrl) != 1){
            log.debug("更新学生作业批阅文件信息失败");

            throw new ServiceException("更新批阅文件失败");
        }
    }

    /**
     * 删除批阅文件
     * @param testId 作业编号
     * @param studentId 学生编号
     *
     * @author fanruiping
     * @since 2015-04-06
     */
    public void deleteReadOverFile(Long testId,Long studentId){
        //判断清空批阅文件信息是否失败
        if(studentTestDao.deleteReadOverFile(testId,studentId) != 1){
            log.debug("清空批阅文件信息失败:studentTestDao.deleteReadOverFile(testId,studentId) != 1");

            throw new ServiceException("清空批阅文件失败");
        }
	}

	/**
     * 获取学生答题明细（用于复习）
     *
     * @param studentId 学生ID
     * @param testId 作业ID
     * @param exerciseId 习题ID
     * @return Exercise
     *
     * @author cx
     * @since 2015/4/3
     */
    public Exercise getTEForReview(long studentId, final long testId, final long exerciseId) {
        //检查习题是否属于作业
        checkTestExercise(testId, exerciseId);

        boolean showTips = true; //控制提示显示
        boolean showAnswer = true; //控制答案显示
        boolean showTestRefInfo = true; //控制作业答题敏感信息显示（知识点，解析，回答等）

        if (webContext.getRole() == Roles.STUDENT) { //登录用户为学生时
            log.debug("登录用户为学生:webContext.getRole() == Roles.STUDENT");

            //获取作业的构建信息
            Test test = testDao.getBuildInfo(testId);

            //判断作业是否开始
            if (isBeforeTime(test)) {
                log.debug("作业未开始:isBeforeTime(test)");
                throw new ServiceException("作业未到开始时间");
            }

            //检查作业是否属于登录学生
            checkStudentTest(testId);
            studentId = webContext.getUserId();
            boolean isOvertime = isOverTime(test);

            //是否显示提示
            if (test.getShowTips() == 0 && !isOvertime) {
                log.debug("提示显示标识:test.getShowTips() == 0 && !isOvertime");
                showTips = false;
            }

            //判断作业是否达到结束时间
            if (!isOvertime) {
                log.debug("作业未超出结束时间:!isOvertime");
                showAnswer = false;
                showTestRefInfo = false;
            }
        }
        else {
            log.debug("登录用户为教师");

            //检查作业是否属于当前教师
            checkTeacherHasTest(webContext.getUserId(), testId);
        }

        //获取学生作业及答题信息
        Exercise exercise = exerciseService.getTEWithAnswer(studentId, testId, exerciseId, showTips, showTestRefInfo, showAnswer);

        log.debug("return 习题答题明细");
        return exercise;
    }

    /**
     * 获取学生作业明细（简单）
     * @param testId 作业编号
     * @param studentId 学生编号
     * @return StudentTest
     *
     * @author fanruiping
     * @since 2015-04-11
     */
    public StudentTest getStudentTestSimpleDetail(Long testId, Long studentId){
        return studentTestDao.getDetailByStudentId(testId,studentId);
    }

    /**
     * 检查作业是否属于指定教师
     * @param teacherId 教师编号
     * @param testId 作业作业
     *
     * @author fanruiping
     * @since 2015-04-14
     */
    public void checkTeacherHasTest(Long teacherId, Long testId) {
        //判断作业是否属于指定教师
        if (!testDao.checkTeacherTest(teacherId, testId)) {
            log.debug("指定作业不属于指定教师:!testDao.checkTeacherTest(teacherId, testId)");

            log.warn("指定作业不属于指定教师");

            throw new ServiceException("操作数据有误");
        }
    }

    /**
     * 获取作业习题的习题资源
     * @param testId 作业ID
     * @param exerciseId 习题ID
     * @return List
     * 
     * @author cx
     * @since 2015/4/23
     */
    public List<Resource> getResourcesOfTE(final long testId, final long exerciseId) {
        //校验习题是否属于作业
        checkTestExercise(testId, exerciseId);

        if (Roles.STUDENT == webContext.getRole()) { //登录用户为学生
            log.debug("登录用户为学生:webContext.getRole() == Roles.STUDENT");

            //获取作业的构建信息
            Test test = testDao.getBuildInfo(testId);
            //判断作业是否开始
            if (isBeforeTime(test)) {
                log.debug("作业未开始:isBeforeTime(test)");
                throw new ServiceException("作业未到开始时间");
            }

            //检查作业是否属于登录学生
            checkStudentTest(testId);
            boolean isOvertime = isOverTime(test);

            //作业未达到结束时间
            if (!isOvertime) {
                log.debug("作业未达到结束时间:isOvertime");
                return new ArrayList<>(0);
            }
        }
        else { //登录用户为教师
            log.debug("登录用户为教师");
            //检查作业是否属于当前教师
            checkTeacherHasTest(webContext.getUserId(), testId);
        }

        //获取习题资源解析
        return exerciseService.getResAnalysis(exerciseId);
    }

    /**
     * 获取学生线下作业详细
     * @param studentId 学生编号
     * @param testId 作业编号
     * @return StudentTest
     *
     * @author fanruiping
     * @since 2015-04-28
     */
    public StudentTest getSTFileInfo(Long studentId, Long testId){
        return testDao.getSTFileInfo(studentId, testId);
    }
}
